﻿using System;
using System.IO;

namespace SocketCore
{
    internal class SerializerHelper
    {
        // Fields
        private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        // Methods
        private static void CheckRange(Stream stream, int neededLen)
        {
            long available = stream.Length - stream.Position;
            if (neededLen > available)
            {
                throw new IndexOutOfRangeException();
            }
        }

        public static bool ReadBoolean(Stream stream)
        {
            return (ReadByte(stream) == 1);
        }

        public static byte ReadByte(Stream stream)
        {
            CheckRange(stream, 1);
            return (byte) stream.ReadByte();
        }

        public static byte[] ReadBytes(Stream stream)
        {
            int len = -1;
            try
            {
                len = ReadInt(stream);
                CheckRange(stream, len);
            }
            catch
            {
                return null;
            }
            byte[] res = new byte[len];
            stream.Read(res, 0, len);
            return res;
        }

        public static char ReadChar(Stream stream)
        {
            return (char) ((ushort) ReadShort(stream));
        }

        public static DateTime ReadDate(Stream stream)
        {
            long timeFromEpoch = ReadLong(stream);
            TimeSpan timeSpan = new TimeSpan(timeFromEpoch * 10000);
            DateTime tmp = epoch + timeSpan;
            return tmp.ToLocalTime();
        }

        public static DateTime ReadDateTime(Stream stream)
        {
            int year = ReadInt(stream);
            int mon = ReadInt(stream);
            int day = ReadInt(stream);
            int hour = ReadInt(stream);
            int min = ReadInt(stream);
            int sec = ReadInt(stream);
            return new DateTime(year, mon, day, hour, min, sec, ReadInt(stream));
        }

        public static float ReadFloat(Stream stream)
        {
            return BitConverter.ToSingle(BitConverter.GetBytes(ReadInt(stream)), 0);
        }

        public static int ReadInt(Stream stream)
        {
            CheckRange(stream, 4);
            int i = 0;
            for (int j = 0; j < 4; j++)
            {
                i = i << 8;
                i |= stream.ReadByte();
            }
            return i;
        }

        public static long ReadLong(Stream stream)
        {
            CheckRange(stream, 8);
            long l = 0L;
            for (int i = 0; i < 8; i++)
            {
                l = l << 8;
                l |= stream.ReadByte();
            }
            return l;
        }

        public static short ReadShort(Stream stream)
        {
            CheckRange(stream, 2);
            short s = 0;
            for (int i = 0; i < 2; i++)
            {
                s = (short) (s << 8);
                s = (short) (s | ((short) stream.ReadByte()));
            }
            return s;
        }

        public static string ReadString(Stream stream)
        {
            int len = ReadInt(stream);
            if (len > 1000000)
            {
                return "";
            }
            char[] chars = new char[len];
            for (int i = 0; i < len; i++)
            {
                try
                {
                    chars[i] = ReadChar(stream);
                }
                catch
                {
                    return null;
                }
            }
            return new string(chars);
        }

        public static void WriteBoolean(bool b, Stream stream)
        {
            if (b)
            {
                stream.WriteByte(1);
            }
            else
            {
                stream.WriteByte(0);
            }
        }

        public static void WriteByte(byte b, Stream stream)
        {
            stream.WriteByte(b);
        }

        public static void WriteBytes(byte[] b, Stream stream)
        {
            WriteBytes(b, 0, b.Length, stream);
        }

        public static void WriteBytes(byte[] b, int offset, int count, Stream stream)
        {
            WriteInt(count, stream);
            stream.Write(b, offset, count);
        }

        public static void WriteChar(char c, Stream stream)
        {
            short s = (short) c;
            WriteShort(s, stream);
        }

        public static void WriteDate(DateTime dateTime, Stream stream)
        {
            TimeSpan span = (TimeSpan) (DateTime.UtcNow - epoch);
            long timeFromEpoch = (long) span.TotalMilliseconds;
            WriteLong(timeFromEpoch, stream);
        }

        public static void WriteDateTime(DateTime dateTime, Stream stream)
        {
            WriteInt(dateTime.Year, stream);
            WriteInt(dateTime.Month, stream);
            WriteInt(dateTime.Day, stream);
            WriteInt(dateTime.Hour, stream);
            WriteInt(dateTime.Minute, stream);
            WriteInt(dateTime.Second, stream);
            WriteInt(dateTime.Millisecond, stream);
        }

        public static void WriteInt(int i, Stream stream)
        {
            for (int j = 3; j >= 0; j--)
            {
                stream.WriteByte((byte) (i >> (j * 8)));
            }
        }

        public static void WriteLong(long l, Stream stream)
        {
            for (int i = 7; i >= 0; i--)
            {
                stream.WriteByte((byte) (l >> (i * 8)));
            }
        }

        public static void WriteShort(short s, Stream stream)
        {
            for (int i = 1; i >= 0; i--)
            {
                stream.WriteByte((byte) (s >> (i * 8)));
            }
        }

        public static void WriteString(string s, Stream stream)
        {
            if (s == null)
            {
                s = string.Empty;
            }
            int len = s.Length;
            WriteInt(len, stream);
            for (int i = 0; i < len; i++)
            {
                char c = s[i];
                WriteChar(c, stream);
            }
        }
    }
}
